package toolsdk

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strings"
)

type ApiSign struct {
	secret  string
	params  map[string]interface{}
	signStr string
}

func (m *ApiSign) SetSecret(s string) *ApiSign {
	m.secret = s
	return m
}

func (m *ApiSign) GetSecret() string {
	return m.secret
}

func (m *ApiSign) SetJsonData(str string) *ApiSign {
	toD := map[string]interface{}{}
	_ = json.Unmarshal([]byte(str), &toD)
	m.params = toD
	return m
}

func (m *ApiSign) SetData(p map[string]interface{}) *ApiSign {
	m.params = p
	return m
}

func (m *ApiSign) AppendData(k string, v interface{}) *ApiSign {
	m.params[k] = v
	return m
}

func (m *ApiSign) GetData() map[string]interface{} {
	return m.params
}

// 签名
func (m *ApiSign) MakeSign() string {
	sign := ""
	signData := map[string]interface{}{}
	var keys []string
	keys, signData = m.handler(m.params)
	sort.Strings(keys)

	builder := strings.Builder{}
	if len(keys) != 0 {
		for _, v := range keys {
			tmpVal := ToStr(signData[v])
			builder.WriteString(fmt.Sprintf("%s=%s&", v, tmpVal))
		}
		builder.WriteString("key=" + m.secret)
	} else {
		builder.WriteString("&key=" + m.secret)
	}
	m.signStr = builder.String()
	sign = strings.ToUpper(m.md5Val(m.signStr))

	return sign
}

func (m ApiSign) handler(d map[string]interface{}) ([]string, map[string]interface{}) {
	signData := map[string]interface{}{}
	var keys []string
	supportType := []reflect.Kind{
		reflect.Float64, reflect.Float32,
		reflect.String, reflect.Bool,
		reflect.Int, reflect.Int64,
		reflect.Int8, reflect.Int16, reflect.Int32,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
	}
	for k, v := range d {
		if k == "sign" || v == "" || v == nil {
			continue
		}
		typeName := reflect.TypeOf(v).Kind()
		if typeName == reflect.Ptr { // 存在指针
			tmpD, _ := json.Marshal(d)
			toD := map[string]interface{}{}
			_ = json.Unmarshal(tmpD, &toD)
			return m.handler(toD)
		}
		if m.kindInSlice(typeName, supportType) == false {
			continue
		}
		ref := reflect.ValueOf(v)
		if ref.IsZero() {
			continue
		}
		if typeName == reflect.Bool {
			tmpVal, _ := v.(bool)
			if tmpVal == false {
				continue
			}
			signData[k] = 1
		} else {
			signData[k] = v
		}
		keys = append(keys, k)
	}
	return keys, signData
}

func (m ApiSign) md5Val(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}

func (m ApiSign) kindInSlice(a reflect.Kind, list []reflect.Kind) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func (m ApiSign) GetSignString() string {
	return m.signStr
}

func NewApiSign() *ApiSign {
	apisign := new(ApiSign)
	apisign.params = make(map[string]interface{})
	return apisign
}
